/******************************************************************************
*	device/hid/keyboard.c
*	 by Alex Chadwick
*
*	A light weight implementation of the USB protocol stack fit for a simple
*	driver.
*
*	device/hid/keyboard.c contains code relating to USB hid keyboards. The
*	driver maintains a list of the keyboards on the system, and allows the
*	operating system to retrieve the status of each one separately. It is coded
*	a little awkwardly on purpose to make OS development more fun!
******************************************************************************/
#include <device/hid/hid.h>
#include <device/hid/keyboard.h>
#include <device/hid/report.h>
#include <platform/platform.h>
#include <types.h>
#include <usbd/device.h>
#include <usbd/usbd.h>

#define  KeyboardMaxKeyboards  (4)  // Must be a multiple of 4

UsbDevice  *keyboards[KeyboardMaxKeyboards];

u32        keyboardCount ALIGN4                    = 0;
u32        keyboardAddresses[KeyboardMaxKeyboards] = { 0, 0, 0, 0 };

//+=============================================================================
// Func: KbdLoad
// Desc: Initialise the keyboard details
// Args: -none-
// Retn: -none-
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
void  KbdLoad (void)
{
	u32  kbd;

	LOG_DEBUG("CSUD: Keyboard driver version 0.1\n");

	keyboardCount = 0;
	for (kbd = 0;  kbd < KeyboardMaxKeyboards;  kbd++) {
		keyboardAddresses[kbd] = 0;
		keyboards[kbd]         = NULL;
	}
	HidUsageAttach[DesktopKeyboard] = KeyboardAttach;

	return;
}

//+=============================================================================
// Func: KeyboardIndex
// Desc: Find the index of the keyboard from its address
// Args: u32 address .. Address of keyboard
// Retn: u32 .. Index in to keyboardAddresses -or- 0xFFFFFFFF if not found
// Note: -
// Auth: Chadderz; BlueChip
//==============================================================================
u32  KeyboardIndex (u32 address)
{
	u32  kbd;

	if (address == 0)  return 0xFFFFFFFF ;

	for (kbd = 0;  kbd < keyboardCount;  kbd++)
		if (keyboardAddresses[kbd] == address)  return kbd ;

	return 0xFFFFFFFF;
}

//+=============================================================================
// Func: KeyboardGetAddress
// Desc: Returns the device-address of the n'th connected keyboard.
// Args: u32 index .. Index in to keyboardAddresses
// Retn: u32 .. Address of keyboard -or- 0 if keyboard not found
// Note: Keyboards that are connected are stored in an array
//         This function retrieves the n'th item from that array.
//     : ==DEPRECATED==
// Auth: Chadderz; BlueChip
//==============================================================================
/* DEPRECATED
u32  KeyboardGetAddress (u32 index)
{
	u32  kbd;

	if (index > keyboardCount)  return 0 ;

	for (kbd = 0;  (index >= 0) && (kbd < KeyboardMaxKeyboards);  kbd++)
		if (keyboardAddresses[kbd] != 0) {
			if (index == 0)  return keyboardAddresses[kbd] ;
			index--;
		}

	return 0;
}
*/

//+=============================================================================
// Func: KeyboardDetached
// Desc:
// Args: UsbDevice* device ..
// Retn: -none-
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
void  KeyboardDetached (UsbDevice* device)
{
	KeyboardDevice  *data;

	data = (KeyboardDevice*)((HidDevice*)device->DriverData)->DriverData;
	if (data != NULL) {
		if (keyboardAddresses[data->Index] == device->Number) {
			keyboardAddresses[data->Index] = 0;
			keyboards[data->Index]         = NULL;
			keyboardCount--;
		}
	}

	return;
}

//+=============================================================================
// Func: KeyboardDeallocate
// Desc: Deallocate keyboard from USB Stack
// Args: UsbDevice* device ..
// Retn: -none-
// Note: -
// Auth: Chadderz; BlueChip
//==============================================================================
void  KeyboardDeallocate (UsbDevice* device)
{
	HidDevice  *hdr = (HidDevice*)device->DriverData;

	if (hdr->DriverData != NULL) {
		MemoryDeallocate(hdr->DriverData);
		hdr->DriverData = NULL;
	}
	hdr->HidDeallocate = NULL;
	hdr->HidDetached   = NULL;

	return;
}

//+=============================================================================
// Func: KeyboardAttach
// Desc: Enumerates a device as a keyboard.
// Args: UsbDevice* device    ..
//     : u32        interface ..
// Retn: Result .. A csud 'Result' code
// Note: Checks a device already checked by HidAttach to see if it appears to be
//         a keyboard. If so, build up necessary information to enable the
//         keyboard methods.
// Auth: Chadderz; BlueChip
//==============================================================================
Result  KeyboardAttach (UsbDevice* device, u32 interface)
{
	u32              kbd,  i,  j;
	HidDevice        *hidData;
	KeyboardDevice   *data;
	HidParserResult  *parse;

	// Sanity check the driver build
	if ((KeyboardMaxKeyboards & 3) != 0) {
		LOG("KBD: Warning! KeyboardMaxKeyboards not a multiple of 4. "
		    "The driver wasn't built for this!\n");
	}

	// Make sure we are not trying to connect too many keybaords
	if (keyboardCount == KeyboardMaxKeyboards) {
		LOGF("KBD: %s not connected. Too many keyboards connected (%d/%d). "
		     "Change KeyboardMaxKeyboards in device.keyboard.c to allow more.\n",
		     UsbGetDescription(device), keyboardCount, KeyboardMaxKeyboards);
		return ErrorIncompatible;
	}

	// Check we have a HID device
	hidData = (HidDevice*)device->DriverData;
	if (hidData->Header.DeviceDriver != DeviceDriverHid) {
		LOGF("KBD: %s isn't a HID device. "
		     "The keyboard driver is built upon the HID driver.\n",
		     UsbGetDescription(device));
		return ErrorIncompatible;
	}

	// Check the device reports itself as being a keyboard
	parse = hidData->ParserResult;
	if ((parse->Application.Page    != GenericDesktopControl) ||
	    (parse->Application.Desktop != DesktopKeyboard)) {
		LOGF("KBD: %s doesn't seem to be a keyboard...\n", UsbGetDescription(device));
		return ErrorIncompatible;
	}

	//?
	if (parse->ReportCount < 1) {
		LOGF("KBD: %s doesn't have enough outputs to be a keyboard.\n",
		     UsbGetDescription(device));
		return ErrorIncompatible;
	}

	//?
	hidData->HidDetached     = KeyboardDetached;
	hidData->HidDeallocate   = KeyboardDeallocate;
	hidData->DriverData      = MemoryAllocate(sizeof(KeyboardDevice));
	if (hidData->DriverData == NULL) {
		LOGF("KBD: Not enough memory to allocate keyboard %s.\n",
		     UsbGetDescription(device));
		return ErrorMemory;
	}

	//?
	data                       = (KeyboardDevice*)hidData->DriverData;
	data->Header.DeviceDriver  = DeviceDriverKeyboard;
	data->Header.DataSize      = sizeof(KeyboardDevice);
	data->Index                = 0xFFFFFFFF;

	// Try and find a free slot for this keyboard
	kbd = 0xFFFFFFFF;
	for (i = 0;  i < KeyboardMaxKeyboards;  i++) {
		if (keyboardAddresses[i] == 0) {
			data->Index          = i;
			kbd                  = i;
			keyboardAddresses[i] = device->Number;
			keyboardCount++;
			break;
		}
	}
	// If we failed to find a free slot, we have a problem
	if (kbd == 0xFFFFFFFF) {
		//! I do not believe this error message to either informative or correct
		//! A slot was not found, so the keybaordCount was never increased.
		LOG("KBD: PANIC! Driver in inconsistent state! KeyboardCount is inaccurate.\n");
		KeyboardDeallocate(device);
		return ErrorGeneral;
	}
	keyboards[kbd] = device;

	for (i = 0;  i < KeyboardMaxKeys;  i++)  data->Keys[i]      = 0 ;
	for (i = 0;  i <               9;  i++)  data->KeyFields[i] = NULL ;
	for (i = 0;  i <               8;  i++)  data->LedFields[i] = NULL ;
	*(u8*)&data->Modifiers  = 0;
	*(u8*)&data->LedSupport = 0;
	data->KeyReport         = NULL;
	data->LedReport         = NULL;

	for (i = 0;  i < parse->ReportCount;  i++) {
		if ((parse->Report[i]->Type == Input) && (data->KeyReport == NULL)) {
			LOG_DEBUGF("KBD: Output report %d. %d fields.\n",
			           i, parse->Report[i]->FieldCount);

			data->KeyReport = parse->Report[i];
			for (j = 0;  j < parse->Report[i]->FieldCount;  j++) {
				HidParserField  *pfld = &parse->Report[i]->Fields[j];

				if (pfld->Usage.Page == KeyboardControl) {
					if (pfld->Attributes.Variable) {  //! == what?
						if ((pfld->Usage.Keyboard >= KeyboardLeftControl) &&
						    (pfld->Usage.Keyboard <= KeyboardRightGui))
							LOG_DEBUGF("KBD: Modifier %d detected! Offset=%x, size=%x\n",
							           pfld->Usage.Keyboard, pfld->Offset, pfld->Size);
							data->KeyFields[(u16)pfld->Usage.Keyboard -
							                (u16)KeyboardLeftControl] = pfld;

					} else {
						LOG_DEBUG("KBD: Key input detected!\n");
						data->KeyFields[8] = pfld;
					}
				}
			} // for j

		} else if ((parse->Report[i]->Type == Output) && (data->LedReport == NULL)) {
			LOG_DEBUGF("KBD: Input report %d. %d fields.\n",
			           i, parse->Report[i]->FieldCount);

			data->LedReport = parse->Report[i];
			for (j = 0;  j < parse->Report[i]->FieldCount;  j++) {
				HidParserField  *pfld = &parse->Report[i]->Fields[j];

				if (pfld->Usage.Page == Led) {
					switch (pfld->Usage.Led) {
						case LedNumberLock:
							LOG_DEBUG("KBD: Number lock LED detected!\n");
							data->LedFields[0]          = pfld;
							data->LedSupport.NumberLock = true;
							break;

						case LedCapsLock:
							LOG_DEBUG("KBD: Caps lock LED detected!\n");
							data->LedFields[1]        = pfld;
							data->LedSupport.CapsLock = true;
							break;

						case LedScrollLock:
							LOG_DEBUG("KBD: Scroll lock LED detected!\n");
							data->LedFields[2]          = pfld;
							data->LedSupport.ScrollLock = true;
							break;

						case LedCompose:
							LOG_DEBUG("KBD: Compose LED detected!\n");
							data->LedFields[3]       = pfld;
							data->LedSupport.Compose = true;
							break;

						case LedKana:
							LOG_DEBUG("KBD: Kana LED detected!\n");
							data->LedFields[4]    = pfld;
							data->LedSupport.Kana = true;
							break;

						case LedPower:
							LOG_DEBUG("KBD: Power LED detected!\n");
							data->LedFields[5]     = pfld;
							data->LedSupport.Power = true;
							break;

						case LedShift:
							LOG_DEBUG("KBD: Shift LED detected!\n");
							data->LedFields[6]     = pfld;
							data->LedSupport.Shift = true;
							break;

						case LedMute:
							LOG_DEBUG("KBD: Mute LED detected!\n");
							data->LedFields[7]    = pfld;
							data->LedSupport.Mute = true;
							break;

						default:
							break;
					} // switch (pfld->Usage.Led)
				} // if (pfld->Usage.Page == Led)
			} // for j
		} // if input|output
	} // for i

	LOG_DEBUGF("KBD: New keyboard assigned %d!\n", device->Number);

	return OK;
}

//+=============================================================================
// Func: KeyboardCount
// Desc: Returns the number of keyboards connected to the system.
// Args: -none-
// Retn: u32 .. The number of keyboards connected to the system
// Note: -
// Auth: Chadderz; BlueChip
//==============================================================================
u32  KeyboardCount (void)
{
	return keyboardCount;
}

//+=============================================================================
// Func: KeyboardSetLeds
// Desc: Sets the state of the keyboard LEDs
// Args: u32          keyboardAddress ..
//     : KeyboardLeds leds            .. Required LED state
// Retn: Result .. A csud 'Result' code
// Note: Unimplemented LEDs are silently ignored
// Auth: Chadderz; BlueChip
//==============================================================================
Result  KeyboardSetLeds (u32 keyboardAddress, KeyboardLeds leds)
{
	u32             kbd;
	KeyboardDevice  *data;

	kbd = KeyboardIndex(keyboardAddress);
	if (kbd == 0xFFFFFFFF)  return ErrorDisconnected ;

	data = (KeyboardDevice*)((HidDevice*)keyboards[kbd]->DriverData)->DriverData;
	if (data->LedSupport.NumberLock)  data->LedFields[0]->Value.Bool = leds.NumberLock ;
	if (data->LedSupport.CapsLock)    data->LedFields[1]->Value.Bool = leds.CapsLock ;
	if (data->LedSupport.ScrollLock)  data->LedFields[2]->Value.Bool = leds.ScrollLock ;
	if (data->LedSupport.Compose)     data->LedFields[3]->Value.Bool = leds.Compose ;
	if (data->LedSupport.Kana)        data->LedFields[4]->Value.Bool = leds.Kana ;
	if (data->LedSupport.Power)       data->LedFields[5]->Value.Bool = leds.Power ;
	if (data->LedSupport.Shift)       data->LedFields[6]->Value.Bool = leds.Shift ;
	if (data->LedSupport.Mute)        data->LedFields[7]->Value.Bool = leds.Mute ;

	return HidWriteDevice(keyboards[kbd], data->LedReport->Index);
}

//+=============================================================================
// Func: KeyboardGetLedSupport
// Desc: Gets a list of available keyboard LEDs
// Args: u32 keyboardAddress ..
// Retn: KeyboardLeds ..
// Note: Reads the availablility of keyboard LEDs from the report descriptor.
//     : LEDs that are present are set to 1; LEDs not present are set to 0.
// Auth: Chadderz; BlueChip
//==============================================================================
KeyboardLeds  KeyboardGetLedSupport (u32 keyboardAddress)
{
	u32             kbd;
	KeyboardDevice  *data;
	KeyboardLeds    empty_leds;

	kbd = KeyboardIndex(keyboardAddress);
	if (kbd == 0xFFFFFFFF)  return empty_leds ;

	data = (KeyboardDevice*)((HidDevice*)keyboards[kbd]->DriverData)->DriverData;
	return data->LedSupport;
}

//+=============================================================================
// Func: KeyboardPoll
// Desc: Checks the specified keyboard
// Args: u32 keyboardAddress ..
// Retn: Result .. A csud 'Result' code
// Note: Reads back the report for the specifiedn keyboard and parses it in to
//         the internal fields.
//     : The 'internal fields' can be accessed with KeyboardGet??? methods
// Auth: Chadderz; BlueChip
//==============================================================================
Result  KeyboardPoll (u32 keyboardAddress)
{
	u32             kbd,  i;
	Result          result;
	KeyboardDevice  *data;

	kbd = KeyboardIndex(keyboardAddress);
	if (kbd == 0xFFFFFFFF)  return ErrorDisconnected ;

	data   = (KeyboardDevice*)((HidDevice*)keyboards[kbd]->DriverData)->DriverData;
	result = HidReadDevice(keyboards[kbd], data->KeyReport->Index);
	if (result != OK) {
		if (result != ErrorDisconnected)
			LOGF("KBD: Could not get key report from %s.\n",
			     UsbGetDescription(keyboards[kbd]));
		return result;
	}

	if (data->KeyFields[0])  data->Modifiers.LeftControl  = data->KeyFields[0]->Value.Bool;
	if (data->KeyFields[1])  data->Modifiers.LeftShift    = data->KeyFields[1]->Value.Bool;
	if (data->KeyFields[2])  data->Modifiers.LeftAlt      = data->KeyFields[2]->Value.Bool;
	if (data->KeyFields[3])  data->Modifiers.LeftGui      = data->KeyFields[3]->Value.Bool;
	if (data->KeyFields[4])  data->Modifiers.RightControl = data->KeyFields[4]->Value.Bool;
	if (data->KeyFields[5])  data->Modifiers.RightShift   = data->KeyFields[5]->Value.Bool;
	if (data->KeyFields[6])  data->Modifiers.RightAlt     = data->KeyFields[6]->Value.Bool;
	if (data->KeyFields[7])  data->Modifiers.RightGui     = data->KeyFields[7]->Value.Bool;
	if (data->KeyFields[8]) {
		if (HidGetFieldValue(data->KeyFields[8], 0) != KeyboardErrorRollOver) {
			data->KeyCount = 0;
			for (i = 0;  (i < KeyboardMaxKeys) && (i < data->KeyFields[8]->Count);  i++) {
				data->Keys[i] = HidGetFieldValue(data->KeyFields[8], i);
				if ((data->Keys[i] + (u16)data->KeyFields[8]->Usage.Keyboard) != 0)
					data->KeyCount++;
			}
		}
	}

	return OK;
}

//+=============================================================================
// Func: KeyboardModifiers
// Desc: Read the modifier keys from a keyboard.
// Args: u32 keyboardAddress .. Address of keyboard
// Retn: KeyboardModifiers .. Modifier keys state
// Note: Reads back the state of the modifier keys from the last sucessfully
//         received report. Zeros out by default.
// Auth: Chadderz; BlueChip
//==============================================================================
KeyboardModifiers  KeyboardGetModifiers (u32 keyboardAddress)
{
	u32                kbd;
	KeyboardDevice     *data;
	KeyboardModifiers  empty_mods;

	kbd = KeyboardIndex(keyboardAddress);
	if (kbd == 0xFFFFFFFF)  return empty_mods ;

	data = (KeyboardDevice*)((HidDevice*)keyboards[kbd]->DriverData)->DriverData;
	return data->Modifiers;
}

//+=============================================================================
// Func: KeyboardGetKeyDownCount
// Desc: Returns the number of depressed keys
// Args: u32 keyboardAddress ..
// Retn: u32 .. Number of depressed keys
// Note: Reads back the number of keys that were held down in the last report.
//     : If the keyboard reaches its key limit, this reports the last
//         sensible value received.
// Auth: Chadderz; BlueChip
//==============================================================================
u32  KeyboardGetKeyDownCount (u32 keyboardAddress)
{
	u32             kbd;
	KeyboardDevice  *data;

	kbd = KeyboardIndex(keyboardAddress);
	if (kbd == 0xFFFFFFFF)  return 0 ;

	data = (KeyboardDevice*)((HidDevice*)keyboards[kbd]->DriverData)->DriverData;
	return data->KeyCount;
}

//+=============================================================================
// Func: KeyboadGetKeyIsDown
// Desc: Returns whether or not a particular key is depressed
// Args: u32 keyboardAddress ..
//     : u16 key             ..
// Retn: bool .. true if key is depressed, else false
// Note: Reads back whether or not a key was held on the last successfully
//         received report.
// Auth: Chadderz; BlueChip
//==============================================================================
bool  KeyboadGetKeyIsDown (u32 keyboardAddress, u16 key)
{
	u32             kbd,  i;
	KeyboardDevice  *data;

	kbd = KeyboardIndex(keyboardAddress);
	if (kbd == 0xFFFFFFFF)  return false ;

	data = (KeyboardDevice*)((HidDevice*)keyboards[kbd]->DriverData)->DriverData;
	for (i = 0;  i < data->KeyCount;  i++)
		if (data->Keys[i] == key)  return true ;

	return false;
}

//+=============================================================================
// Func: KeyboardGetKeyDown
// Desc: Returns the n'th depressed key
// Args: u32 keyboardAddress ..
//     : u32 index           ..
// Retn: u16 ..
// Note: Reads back the number of the n'th depressed key from the last
//         successfully received report.
// Auth: Chadderz; BlueChip
//==============================================================================
u16  KeyboardGetKeyDown (u32 keyboardAddress, u32 index)
{
	u32             kbd;
	KeyboardDevice  *data;
	u32             keyCount = KeyboardGetKeyDownCount(keyboardAddress);

	kbd = KeyboardIndex(keyboardAddress);
	if (kbd == 0xFFFFFFFF)  return 0 ;

	data = (KeyboardDevice*)((HidDevice*)keyboards[kbd]->DriverData)->DriverData;
	if (index >= keyCount)  return 0 ;

	return data->Keys[index];
}
